/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import Dep, { pushTarget, popTarget } from '../observer/dep'
import { isUpdatingChildComponent } from './lifecycle'

import {
  set,
  del,
  observe,
  defineReactive,
  toggleObserving
} from '../observer/index'

import {
  warn,
  bind,
  noop,
  hasOwn,
  hyphenate,
  isReserved,
  handleError,
  nativeWatch,
  validateProp,
  isPlainObject,
  isServerRendering,
  isReservedAttribute,
  invokeWithErrorHandling
} from '../util/index'

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

//
export function proxy (target: Object, sourceKey: string, key: string) {
    // 做响应式代理，在vue中， 值姐可以通过this.xxx 来获取，是因为中间做了一层代理   sourceKey、
    // 在data 中， sourceKey 就是_data
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

//初始化一些状态
export function initState (vm: Component) {
  //给实例上新增了一个属性_watchers，用来存储当前实例中所有的watcher实例，
  // 无论是使用vm.$watch注册的watcher实例还是使用watch选项注册的watcher实例，都会被保存到该属性中。
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initProps (vm: Component, propsOptions: Object) {
  //vm  当前Vue实例
  //propsOptions 当前实例规范化后的props选项
  const propsData = vm.$options.propsData || {} //父组件传入的真实props数据
  const props = vm._props = {}  //指向vm._props的指针，所有设置到props变量中的属性都会保存到vm._props中。
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []  //指向vm.$options._propKeys的指针，缓存props对象中的key，将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的key。
  const isRoot = !vm.$parent //当前组件是否为根组件。
  // root instance props should be converted
  if (!isRoot) {
    //判断当前组件是否为根组件，如果不是，那么不需要将props数组转换为响应式的
    toggleObserving(false)
  }
  
  //遍历props选项拿到每一对键值，先将键名添加到keys中，然后调用validateProp函数（关于该函数下面会介绍）校验父组件传入的props数据类型是否匹配并获取到传入的值value，
  // 然后将键和值通过defineReactive函数添加到props（即vm._props）中，
  for (const key in propsOptions) {
    //先将键名添加到keys中
    keys.push(key)
    //然后调用validateProp函数校验父组件传入的props数据类型是否匹配并获取到传入的值value
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      //然后将键和值通过defineReactive函数添加到props（即vm._props）中
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}
// 和initMethods 相似
function initData (vm: Component) {
  let data = vm.$options.data
  // 判断data是不是一个函数，如果是就调用getData函数获取其返回值
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  //  如果不是对象 警告
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 循环对比 如果有与method相同的变量，则报错， 因为他们最重队徽挂在到vm 上， 会导致冲突
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    //再判断是否存在某一项的key与prop中某个属性名重复，如果存在重复，就抛出警告
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
        // 代理
      //则调用proxy函数将data对象中key不以_或$开头的属性代理到实例vm上
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  //调用observe函数将data中的数据转化成响应式
  observe(data, true /* asRootData */)
}

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    //
    const userDef = computed[key]
    // 然后判断userDef是不是一个函数，
    // 如果是函数，则该函数默认为取值器getter，将其赋值给变量getter；
    // 如果不是函数，则说明是一个对象，则取对象中的get属性作为取值器赋给变量getter
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    //如果取不到值，则抛出警告
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
    //在服务器渲染的情况下
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    // 判断当前属性名是否在实列vm上，也就是为了重名的判断，重名则抛出警告， 如果不重名，通过defineComputed处理之后挂在到vm上
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

export function defineComputed (target: any, key: string, userDef: Object | Function) {
  //target上定义一个属性key，并且属性key的getter和setter根据userDef的值来设置
  // serDef:设置方式
  
  //如果当前环境是不是服务端渲染，那就不能缓存为false, 否则为true
  const shouldCache = !isServerRendering()
  
  // 判断如果userDef是一个函数，则该函数默认为取值器getter，
  // 此处在非服务端渲染环境下并没有直接使用userDef作为getter，而是调用createComputedGetter函数创建了一个getter，这是因为userDef只是一个普通的getter，它并没有缓存功能，所以我们需要额外创建一个具有缓存功能的getter，
  // 而在服务端渲染环境下可以直接使用userDef作为getter，因为在服务端渲染环境下计算属性不需要缓存。由于用户没有设置setter函数，所以将sharedPropertyDefinition.set设置为noop
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    // 如果userDef是对象。在设置sharedPropertyDefinition.get的时候先判断userDef.get是否存在，
    // 如果不存在，则将其设置为noop，
    // 如果存在，在非服务端渲染环境下并且用户没有明确的将userDef.cache设置为false时调用createComputedGetter函数创建一个getter赋给sharedPropertyDefinition.get。然后设置sharedPropertyDefinition.set为userDef.set函数
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  //再判断在非生产环境下如果用户没有设置setter的话，那么就给setter一个默认函数
  if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { // 判断返回值有无变化， 计算属性的缓存就是通过这个属性来判断的
        //会将读取计算属性的那个watcher添加到计算属性的watcher实例的依赖列表中，
        // 当计算属性中用到的数据发生变化时，
        // 计算属性的watcher实例就会执行watcher.update()方法，
        // 在update方法中会判断当前的watcher是不是计算属性的watcher，
        // 如果是则调用getAndInvoke去对比计算属性的返回值是否发生了变化，
        // 如果真的发生变化，则执行回调，通知那些读取计算属性的watcher重新执行渲染逻辑。
        watcher.evaluate()
      }
      if (Dep.target) {
        //当计算属性所依赖的数据发生了变化，则调用this.get()重新获取计算结果最后返回
        watcher.depend()
      }
      
      return watcher.value
    }
  }
}

function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

function initMethods (vm: Component, methods: Object) {
  //做以下三件事情
  // 判断有没有method
  // method的命名是否规范
  // 挂载到vm实例
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      //如果只有方法名，没有方法体，报错
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      //查看是否props 中的属性和methods 中的某个方法是否有冲突
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      //果methods中某个方法名如果在实例vm中已经存在并且方法名是以_或$开头的，就抛出异常
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    //挂在到vm上
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    
  }
}

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    //拿到每一项的key和对应的值handler
    const handler = watch[key]
    // handler是否为数组，
    // 如果是数组则循环该数组并将数组中的每一项依次调用createWatcher函数来创建watcher；
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      // 如果不是数组，则直接调用createWatcher函数来创建watcher。
      createWatcher(vm, key, handler)
    }
  }
}

//从用户合起来传入的对象中把回调函数cb和参数options剥离出来，然后再以常规的方式调用$watch方法并将剥离出来的参数穿进去
function createWatcher (vm: Component, expOrFn: string | Function, handler: any, options?: Object) {
  //vm:当前实例；
  // expOrFn:被侦听的属性表达式
  // handler:watch选项中每一项的值
  // options:用于传递给vm.$watch的选项对象
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }

  return vm.$watch(expOrFn, handler, options)

}

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {
    const vm: Component = this
    //如果传入的回调函数是个对象，那就表明用户是把第二个参数回调函数cb和第三个参数选项options合起来传入的，此时调用createWatcher函数
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    //如果没有传入，则为空对象
    options = options || {}
    options.user = true

    //创建一个watcher实例
    const watcher = new Watcher(vm, expOrFn, cb, options)

    //判断如果用户在选项参数options中指定的immediate为true，则立即用被观察数据当前的值触发回调
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    //返回一个取消观察函数unwatchFn，用来停止触发回调
    return function unwatchFn () {
      watcher.teardown()  // 从而实现移除实例内数据对其他数据的依赖。
    }
  }
}
